<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>可点击选中单词的富文本</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
      /* 基础样式：保持原有富文本布局 */
      .rich-text-container {
        max-width: 800px;
        margin: 20px auto;
        padding: 20px;
        border: 1px solid #eee;
        border-radius: 8px;
      }
      .rich-text-container p {
        margin: 16px 0;
        line-height: 1.8;
        font-size: 16px;
      }
      .rich-text-container img {
        max-width: 100%;
        height: auto;
        margin: 10px 0;
        border-radius: 4px;
        border: 1px solid #f5f5f5;
      }

      /* 单词span样式：默认状态 + 点击选中状态 */
      .word-span {
        display: inline-block;
        /* padding: 2px 4px; */
        /* margin: 0 2px; */
        /* margin-right: 0.3em; */
        border-radius: 4px;
        cursor: pointer;
        transition: all 0.2s ease;
      }
      /* 鼠标悬浮提示 */
      .word-span:hover {
        background-color: #f0f7ff;
        color: #165dff;
      }
      /* 点击选中状态 */
      .word-span.active {
        background-color: #165dff;
        color: #fff;
      }
      /* 选中时的文本选中效果（避免浏览器默认选中样式冲突） */
      .word-span::selection {
        background-color: transparent;
      }
    </style>
  </head>
  <body>
    <!-- 富文本容器：初始加载原始富文本 -->
    <!-- <div class="rich-text-container" id="richTextContainer">
      <p style="background-color: #ccc;">
        felis <br> ___1___ tellus mollis orci, sed rhoncus pronin sapien nunc accuan eget.
				<span>aaaa</span> 
      </p>
			<button>11111</button>
			<button>___2___</button>
      <p>felis tellus mollis orci, sed rhoncus pronin sapien nunc accuan eget.</p>
      <img src="../背景/000000.png" alt="000" />
      <p>felis tellus mollis orci, sed rhoncus pronin sapien nunc accuan eget.</p>
    </div> -->

    <div id="app">
      <div v-html="richText"></div>
      <hr>
      <div class="rich-text" v-html="richText"></div>

      <button class="@-_$">111</button>
    </div>

  </body>
</html>

<script>
  const { createApp, ref, onMounted } = Vue

  createApp({
    setup() {
      // let richText = `
      //   <div class="rich-text-container" id="richTextContainer">
      //     <p style="background-color: #ccc;">
      //       'felis' 
      //       <br>
      //        ___1___ tellus 'mollis' orci, sed rhoncus pronin sapien nunc accuan eget.
      //       <span>aaaa</span> 
      //     </p>
      //     <button>11111</button>
      //     <button>___2___</button>
      //     <img src="../背景/000000.png" alt="000" />
      //   </div>
      // ` 

      let richText = `
        <div class="rich-text-container" id="richTextContainer">
          <p style="background-color: #ccc;">"felis' 
            ___1___ "eget".
            <span>aaaa</span> 
            <span>"bbbb'</span>
          </p>
          <img src="../背景/000000.png" alt="000" />
        </div>
      ` 

      onMounted(() => {
        let richTextContainer = document.querySelector('.rich-text')
        wrapWordsWithClickableSpan(richTextContainer)
      })

      function wrapWordsWithClickableSpan(richTextContainer) {
        // 递归处理所有节点：只操作文本节点，跳过图片等非文本标签
        function processNode(node) {
          // 情况1：当前是文本节点（且内容非空）
          if ( node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== ''
          ) {
            const text = node.textContent
            // 按「空格」拆分单词（保留标点与单词的关联，如 orci,、eget.）
            // 正则说明：匹配「单词+可选标点」的组合，避免拆分后语义断裂
            const wordRegex = /\b[\w']+[.,!?;:"']?\b/g
            // const wordRegex = /(\s+)|(\w+)|([^\s\w]+)/g;
            
            const parts = text.split(wordRegex) // 拆分出「非单词部分（空格/换行）」
            const words = text.match(wordRegex) || [] // 提取所有「单词+标点」
            console.log( parts, words,  );
            console.log( '--------------------' );

            // 创建文档片段，避免频繁DOM操作
            const fragment = document.createDocumentFragment()
            let partIndex = 0

            // 交替插入「非单词部分」和「单词span」
            words.forEach((word, index) => {
              console.log(partIndex, parts[partIndex], words, word, 11111111111111 );
              // 先插入前面的非单词部分（如空格）
              if (parts[partIndex] && parts[partIndex].trim() === '') {
                fragment.appendChild(
                  document.createTextNode(parts[partIndex])
                )
              }
              partIndex++

              // 创建单词span，添加点击选中逻辑
              const wordSpan = document.createElement('span')
              wordSpan.className = 'word-span'
              wordSpan.textContent = word

              fragment.appendChild(wordSpan)
            })

            // 插入最后剩余的非单词部分
            if (parts[partIndex]) {
              fragment.appendChild(document.createTextNode(parts[partIndex]))
            }

            // 用处理后的片段替换原文本节点
            node.parentNode.replaceChild(fragment, node)
          }

          // 情况2：当前是元素节点（递归处理子节点，跳过图片、br等无需处理的标签）
          else if (node.nodeType === Node.ELEMENT_NODE) {
            // 排除不需要处理的标签（图片、换行等）
            const ignoreTags = ['IMG', 'BR', 'SCRIPT', 'STYLE', 'button']
            if (!ignoreTags.includes(node.tagName)) {
              // 遍历所有子节点（注意：childNodes是动态集合，需转为数组）
              Array.from(node.childNodes).forEach((child) => {
                processNode(child)
              })
            }
          }
        }

        // 开始处理富文本容器的所有节点
        processNode(richTextContainer)
      }
      return {
        wrapWordsWithClickableSpan,
        richText,
      }
    }
  }).mount('#app')
</script>